package org.OpenGeoPortal.Download;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.OpenGeoPortal.Download.LayerDownloader;
import org.OpenGeoPortal.Download.LayerInfoRetriever;
import org.springframework.context.support.*;
import org.springframework.beans.factory.*;

abstract class AbstractDownloadHandler implements DownloadHandler {
	private BoundingBox bounds;
	private BeanFactory injector = null;
	String downloadDirectoryName = "download";
	private File downloadDirectory;
	protected File zipArchive = null;
	protected String layerLink;
	private String springXmlFilePath;
	public List<RequestedLayer> layers = new ArrayList<RequestedLayer>();
	private Map <String, Map <String, String>> layerInfo;
	private Boolean locallyAuthenticated = false;
	private String homeInstitution = "tufts";//this has to be retrieved
	protected LayerInfoRetriever layerInfoRetriever;
	protected DownloadConfigRetriever downloadConfigRetriever;
	private String emailAddress = "";
	
	public void requestLayers(Map<String,String> layerMap, String[] bounds) throws Exception{
		this.bounds = new BoundingBox(bounds[0], bounds[1], bounds[2], bounds[3]);
		this.layerInfo = this.layerInfoRetriever.getAllLayerInfo(layerMap.keySet());
		this.createDownloadDirectory() ;
		this.routeDownloads(this.createDownloadMap(layerMap));
		this.doWork();
	}

	public void setContextConfigPath(String contextConfigPath){
		this.springXmlFilePath = contextConfigPath;
	};
	
	public void bootstrapInjector(String xmlFileName){
		if (this.injector == null){
			this.injector = new FileSystemXmlApplicationContext(xmlFileName);
		}
	}
	
	public void setLocallyAuthenticated(Boolean authenticated){
		this.locallyAuthenticated = authenticated;
	}
	
	public Boolean getLocallyAuthenticated(){
		return this.locallyAuthenticated;
	}
	
	public File getDownloadDirectory(){
		return this.downloadDirectory;
	} 
	
	public LayerDownloader getLayerDownloader(String downloaderKey){
		this.bootstrapInjector(this.springXmlFilePath);//how can I keep this path out of code?
		LayerDownloader layerDownloader = (LayerDownloader) this.injector.getBean(downloaderKey);
		return layerDownloader;
	}
	
	public void routeDownloads(Map <String, List<RequestedLayer>> downloadMap) throws Exception {
		for (String currentDownloader: downloadMap.keySet()){
			//get concrete class key from config
			try{
				LayerDownloader layerDownloader = this.getLayerDownloader(currentDownloader);
				layerDownloader.downloadLayers(downloadMap.get(currentDownloader));
			} catch (Exception e) {
				System.out.println("routeDownloads: " + e.getMessage());
				continue;
			}
		}
	}
	
	/*
	 * map inputs (layerIDs matched to requested formats) to [downloadmethod:<RequestedLayer>]
	 */
	public Map <String, List<RequestedLayer>> createDownloadMap(Map<String,String> layerMap) throws Exception {
		Set<String> layerIdSet = layerMap.keySet();
		Map <String, List<RequestedLayer>> downloadMap = new HashMap<String, List<RequestedLayer>>(); 
		for (String currentLayerId: layerIdSet){
			String currentRequestedFormat = layerMap.get(currentLayerId);
			RequestedLayer currentLayer = this.createLayer(currentLayerId, currentRequestedFormat);
			currentLayer.serviceURL = this.downloadConfigRetriever.getServiceURL(currentLayer);
			if (!currentLayer.accessLevel.toLowerCase().equals("public")){
				if (currentLayer.institution.toLowerCase().equals(this.homeInstitution)){
					if (!this.getLocallyAuthenticated()){
						currentLayer.setStatus(LayerStatus.PERMISSION_DENIED);
						continue;
					}
				} else {
					currentLayer.setStatus(LayerStatus.PERMISSION_DENIED);
					continue;
				}
			}
			String currentClassKey = null;
			try {
				currentClassKey = this.downloadConfigRetriever.getClassKey(currentLayer);
			} catch(Exception e) {
				currentLayer.setStatus(LayerStatus.NO_DOWNLOAD_METHOD);
				//System.out.println(e.getMessage());
				continue;
			}
			if (downloadMap.containsKey(currentClassKey)){
				List<RequestedLayer> currentLayerList = downloadMap.get(currentClassKey);
				currentLayerList.add(currentLayer);
			} else {
				List<RequestedLayer> newLayerList = new ArrayList<RequestedLayer>();
				newLayerList.add(currentLayer);
				downloadMap.put(currentClassKey, newLayerList);
			}
		}
		return downloadMap;
	}
	
	private RequestedLayer createLayer(String layerId, String requestedFormat){
		RequestedLayer layer = new RequestedLayer(layerId, requestedFormat);
		layer.institution = this.layerInfo.get(layerId).get("Institution");
		layer.accessLevel = this.layerInfo.get(layerId).get("Access");
		layer.dataType = this.layerInfo.get(layerId).get("DataType");
		layer.name = this.layerInfo.get(layerId).get("Name");
		layer.workSpace	= this.layerInfo.get(layerId).get("WorkspaceName");
		layer.title	= this.layerInfo.get(layerId).get("LayerDisplayName");
		layer.requestedBounds = this.bounds;
		layer.emailAddress = this.emailAddress;
		layer.nativeBounds = new BoundingBox(this.layerInfo.get(layerId).get("MinX"),this.layerInfo.get(layerId).get("MinY"),
				this.layerInfo.get(layerId).get("MaxX"),this.layerInfo.get(layerId).get("MaxY"));
		layer.targetDirectory = this.getDownloadDirectory();
		this.layers.add(layer);
		return layer;
	}
	
	private void createDownloadDirectory() throws Exception
	{
		//check permissions
		//should this go in the tomcat temp directory? then we don't have to worry so much about paths
		//String userDir = System.getProperty("java.io.tmpdir");
		String relativePath = this.downloadConfigRetriever.getRelativePath();
		String directoryString = "";
		if (relativePath.length() > 0){
			directoryString += this.downloadConfigRetriever.getRelativePath() + "/";
		}
		directoryString += this.downloadDirectoryName;
		
		File downloadDir = new File(directoryString);
		if (!downloadDir.exists()){
			downloadDir.mkdir();
		}
		
		if (!downloadDir.canRead() || !downloadDir.canWrite()){
			throw new IOException("Download directory is inaccessible.");
		}
		this.downloadDirectory = downloadDir;
	}
	
	abstract void doWork();
	
	public abstract String getJsonResponse();
	
	public void setReplyEmail(String emailAddress){
		this.emailAddress = emailAddress;
	}

}
